/*
 * Copyright (c) 2023-2024 elsfs Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.elsfs.cloud.spring.common.configurer;

import java.util.HashSet;
import java.util.Map;
import org.elsfs.cloud.common.properties.ElsfsSecurityProperties;
import org.elsfs.cloud.common.util.http.RequestMethod;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter;
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter;
import org.springframework.web.cors.CorsUtils;

/**
 * 配置安全
 *
 * @author zeng
 */
public abstract class AbstractSecurityCustomConfiguration implements InitializingBean {

  /**
   * 退出配置
   *
   * @param http http
   */
  protected void autoConfigurationOauthLogout(HttpSecurity http) throws Exception {
    http.logout(
        logout ->
            logout
                .addLogoutHandler(
                    (request, response, authentication) -> {
                      System.out.println(authentication.getAuthorities());
                    })
                // 清除身份验证
                .clearAuthentication(true)
                // 使Http会话无效
                .invalidateHttpSession(true)
                // 退出登录的url
                .logoutUrl("/logout")
                .logoutSuccessHandler(
                    (request, response, authentication) -> {
                      System.out.println(authentication.getPrincipal());
                    }));
  }

  /**
   * 添加安全的响应头
   *
   * @param http http
   * @throws Exception 配置异常
   */
  protected void addHttpSecurityHeaders(HttpSecurity http) throws Exception {
    // @formatter:off
    http.headers(
        headers ->
            headers
                // 缓存控制
                // Cache-Control: no-cache, no-store, max-age=0, must-revalidate
                // Pragma: no-cache
                // Expires: 0
                .cacheControl(HeadersConfigurer.CacheControlConfig::disable)
                // 内容嗅探  默认   X-Content-Type-Options: nosniff
                .contentTypeOptions(HeadersConfigurer.ContentTypeOptionsConfig::disable)
                //  X-Frame-Options: DENY
                //  让你的网站被添加到一个 frame
                // 中可能是一个安全问题。例如，通过使用巧妙的CSS样式设计，用户可能会被骗去点击他们不打算点击的东西。例如，一个登录了银行的用户可能会点击一个授予其他用户访问权限的按钮。这类攻击被称为
                // 点击劫持（Clickjacking）。
                .frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)
                //  内容安全策略（Content Security Policy）
                // （CSP）是一种机制，web应用可以用来缓解内容注入漏洞，如跨站脚本（XSS）。CSP是一种声明性的策略，为web应用程序作者提供了一种设施，
                // 以声明并最终告知客户端（user-agent）web应用程序期望加载资源的来源。
                // 一个web应用程序可以通过在响应中包括以下HTTP头信息之一来使用CSP。
                // Content-Security-Policy 和Content-Security-Policy-Report-Only

                //            .contentSecurityPolicy(contentSecurityPolicy -> contentSecurityPolicy
                //                .policyDirectives("")
                //                .reportOnly()
                //            )
                // Referrer Policy 是一种机制，Web应用程序可以用来管理 referrer 字段，它包含用户最后浏览的页面。
                // Referrer-Policy: same-origin
                .referrerPolicy(
                    referrerPolicyConfig ->
                        referrerPolicyConfig.policy(
                            ReferrerPolicyHeaderWriter.ReferrerPolicy.NO_REFERRER)));
    // @formatter:on
  }

  /**
   * 配置 认证的请求
   *
   * @param http http
   * @throws Exception 配置异常
   */
  protected void addHttpSecurityAuthorizeHttpRequests(HttpSecurity http) throws Exception {
    ElsfsSecurityProperties properties = getElsfsSecurityProperties(http);
    Map<RequestMethod, HashSet<String>> ignoreHttpMethod = properties.getIgnoreHttpMethod();

    http.authorizeHttpRequests(
        (authorize) -> {
          authorize.requestMatchers(CorsUtils::isPreFlightRequest).permitAll();
          ignoreHttpMethod.forEach(
              (requestMethod, strings) ->
                  strings.forEach(
                      s -> authorize.requestMatchers(requestMethod.name(), s).permitAll()));
          authorize.anyRequest().authenticated();
        });
  }

  /**
   * oath2Login 配置
   *
   * @param http h
   * @throws Exception e
   */
  protected void autoConfigurationOauthLogin(HttpSecurity http) throws Exception {
    ObjectProvider<OAuth2ClientProperties> auth2ClientPropertiesObjectProvider =
        getOAuth2ClientProperties(http);
    OAuth2ClientProperties properties =
        auth2ClientPropertiesObjectProvider.getIfUnique(OAuth2ClientProperties::new);
    // @formatter:off
    if (!properties.getRegistration().isEmpty()) {
      http.oauth2Login(
          oauth2Login -> {
            // 配置登录页面
            oauth2Login.loginPage("/login");
            // 登录重定向地址
            oauth2Login.redirectionEndpoint(
                r -> r.baseUri(OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI));
            // OAuth2LoginAuthenticationProvider
            // ->OAuth2AuthorizationCodeAuthenticationProvider
            oauth2Login.tokenEndpoint(
                t ->
                    t.accessTokenResponseClient(new DefaultAuthorizationCodeTokenResponseClient()));
            // 成功处理器
            // oauth2Login.successHandler(successHandler);
          });
      //  端点配置
      endpoint(http);
      userInfoEndpoint(http);
      // @formatter:on
    }
  }

  private void userInfoEndpoint(HttpSecurity http) throws Exception {
    http.oauth2Login(
        oauth2Login ->
            oauth2Login.userInfoEndpoint(
                userInfoEndpoint -> {
                  // OAuth2LoginAuthenticationProvider 获取 OAuth2User
                  userInfoEndpoint.userService(new DefaultOAuth2UserService());
                  // OAuth2LoginAuthenticationProvider 获取oauth 权限 并且转换格式添加前缀
                  userInfoEndpoint.userAuthoritiesMapper(new NullAuthoritiesMapper());
                  // oauth2 login 获取OidcUser
                  userInfoEndpoint.oidcUserService(new OidcUserService());
                }));
  }

  private void endpoint(HttpSecurity httpSecurity) throws Exception {
    httpSecurity.oauth2Login(
        oauth2Login ->
            oauth2Login.authorizationEndpoint(
                endpoint -> {
                  // 授权请求存储库 authorizationRequestRepository
                  // 和authorizationRequestResolver 配对使用
                  endpoint.authorizationRequestRepository(
                      new HttpSessionOAuth2AuthorizationRequestRepository());
                  // 此接口的实现能够从提供的HttpServlet请求中解析OAuth2AuthorizationRequest。
                  // 由OAuth2AuthorizationRequestRedirectFilter用于解析授权请求。
                  endpoint.authorizationRequestResolver(
                      new DefaultOAuth2AuthorizationRequestResolver(
                          getClientRegistrationRepository(httpSecurity),
                          OAuth2AuthorizationRequestRedirectFilter
                              .DEFAULT_AUTHORIZATION_REQUEST_BASE_URI));
                }));
  }

  private ClientRegistrationRepository getClientRegistrationRepository(HttpSecurity httpSecurity) {
    return getApplicationContext(httpSecurity).getBean(ClientRegistrationRepository.class);
  }

  protected ElsfsSecurityProperties getElsfsSecurityProperties(HttpSecurity httpSecurity) {
    return getApplicationContext(httpSecurity).getBean(ElsfsSecurityProperties.class);
  }

  private ObjectProvider<OAuth2ClientProperties> getOAuth2ClientProperties(
      HttpSecurity httpSecurity) {

    return getApplicationContext(httpSecurity).getBeanProvider(OAuth2ClientProperties.class);
  }

  private ApplicationContext getApplicationContext(HttpSecurity httpSecurity) {
    return httpSecurity.getSharedObject(ApplicationContext.class);
  }
}
